package plugins

import (
	"context"
	"encoding/json"
	"github.com/micro/go-micro/v2/metadata"
	"github.com/micro/go-micro/v2/util/log"
	"github.com/opentracing/opentracing-go"
	"github.com/uber/jaeger-client-go"
	jaegercfg "github.com/uber/jaeger-client-go/config"
	"github.com/uber/jaeger-lib/metrics"
	"io"
	"time"
)

type jaegerLogger struct{}

// Error logs a message at error priority
func (l *jaegerLogger) Error(msg string) {
	log.WithLevel(log.LevelError, msg)
}

// Infof logs a message at info priority
func (l *jaegerLogger) Infof(msg string, args ...interface{}) {
	log.WithLevelf(log.LevelInfo, msg, args...)
}

func NewJaegerTracer(serviceName string, addr string) (opentracing.Tracer, io.Closer, error) {
	cfg := jaegercfg.Configuration{
		ServiceName: serviceName,
		Sampler: &jaegercfg.SamplerConfig{
			Type:  jaeger.SamplerTypeConst,
			Param: 1,
		},
		Reporter: &jaegercfg.ReporterConfig{
			BufferFlushInterval: 1 * time.Second,
			LogSpans:            true,
		},
	}

	jLogger := &jaegerLogger{}
	jMetrics := metrics.NullFactory

	sender, err := jaeger.NewUDPTransport(addr, 0)
	if err != nil {
		return nil, nil, err
	}

	reporter := jaeger.NewRemoteReporter(sender)
	tracer, closer, err := cfg.NewTracer(
		jaegercfg.Reporter(reporter),
		jaegercfg.Logger(jLogger),
		jaegercfg.Metrics(jMetrics),
	)

	return tracer, closer, err
}

func TracerContext(ctx context.Context, serviceName string) (opentracing.Span, metadata.Metadata, context.Context) {
	span, spanCtx := opentracing.StartSpanFromContext(ctx, serviceName)
	md, ok := metadata.FromContext(spanCtx)
	if !ok {
		md = make(map[string]string)
	}
	md = metadata.Copy(md)

	opentracing.GlobalTracer().Inject(span.Context(), opentracing.TextMap, opentracing.TextMapCarrier(md))
	spanCtx = opentracing.ContextWithSpan(spanCtx, span)
	spanCtx = metadata.NewContext(spanCtx, md)

	return span, md, spanCtx
}

func TracerFinish(span opentracing.Span, serverName string, md metadata.Metadata, req, rsp interface{}) {
	rby, _ := json.Marshal(&req)
	wby, _ := json.Marshal(&rsp)
	defer func() {
		span.LogKV(
			"Server Handler", serverName,
			"Request Params", string(rby),
			"Response Data", string(wby),
			"RemoteIP", md["Remote"],
			"Called Service", md["Micro-From-Service"],
			"TraceID", md["Micro-Trace-Id"],
			"SpanID", md["Micro-Span-Id"],
		)
		span.Finish()
	}()
}

func TracerErrorInfo(span opentracing.Span, serverName string, md metadata.Metadata, req, rsp interface{}, other interface{}, err error) {
	var rby, wby, callby []byte
	if req != nil {
		rby, _ = json.Marshal(&req)
	}
	if rsp != nil {
		wby, _ = json.Marshal(&rsp)
	}
	if other != nil {
		callby, _ = json.Marshal(&other)
	}

	defer func() {
		span.LogKV(
			"Server Handler", serverName,
			"Request Params", string(rby),
			"Response Data", string(wby),
			"Call Response", string(callby),
			"RemoteIP", md["Remote"],
			"Called Service", md["Micro-From-Service"],
			"TraceID", md["Micro-Trace-Id"],
			"SpanID", md["Micro-Span-Id"],
			"Error", err,
		)
		span.Finish()
	}()
}
